package com.zrrd.straw1.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zrrd.straw1.entity.*;
import com.zrrd.straw1.mapper.ClassInfoMapper;
import com.zrrd.straw1.mapper.UserMapper;
import com.zrrd.straw1.mapper.UserRoleMapper;
import com.zrrd.straw1.service.IQuestionService;
import com.zrrd.straw1.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zrrd.straw1.vo.RegisterVo;
import com.zrrd.straw1.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zrrd
 * @since 2024-03-03
 */
@Service
public class UserServiceImpl
        extends ServiceImpl<UserMapper, User>
        implements IUserService {

    @Autowired
    UserMapper userMapper;
    @Autowired
    ClassInfoMapper classInfoMapper;
    @Autowired
    UserRoleMapper userRoleMapper;
    @Autowired
    IQuestionService questionService;

    @Override
    public UserVo getCurrentUserVo(String username) {
        UserVo userVo = userMapper.findUserVoByUsername( username );
        Integer count = questionService.countQuestionsByUserId( userVo.getId() );
        userVo.setQuestions( count );
        return userVo ;
    }

    @Override
    public UserDetails getUserDetails(String username) {
        // 思路：
        // 1.从数据库查，查询条件 username, 结果 -》 用户信息，一个结果
        // 2.先做一个 UserDetails 壳！再把查询到的信息一个一个放到里面。

        // 3.如果是基本的，简单的查询，mybatis-plus已经预先写好了
        //   但是如果复杂的查询逻辑，就需要自行开发。
        // select * from user ;  userMapper.selectOne()但是没有条件
        // select * from user where username = #{username}
        // mybatis-plus 的条件构造器，专门用来保存条件语句的实例。
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.eq("username",username);//where username = #{username}
        User user = userMapper.selectOne( qw );

        //如果数据库中没有此用户名信息，就不必再做后面的认证了。
        if( user == null ){
            return null;
        }
        //list :列表-》有序 -》下标
        List<Permission> list =
            userMapper.findPermissionByUsername(user.getUsername());
        //因为UserDetails方法参数只认字符串数组，
        //所以要对List<Permission> 进行抟换为 String[].
        String[] authorities = new String[ list.size() ];
        /*
        for(int i=0;i<list.size();i++){
            Permission p = list.get(i);
            String authorityName = p.getAuthority();
            authorities[i] = authorityName;
        }
         */
        //优化->想用增强for循环 -> 新循环中没有下标，并且一定是从头到尾遍历
        //而数组必须要用下标！
        int index = 0 ;
        for(Permission p : list ){
            authorities[index++] = p.getAuthority();
        }

        //把用户所有的权限和角色放到同一个数组里，添加到 UserDetails 对象中。
        //在加到用户详情类之前，把用户角色信息添加到 authorities 数组中
        List<Role> roles = userMapper.findUserRoleById( user.getId());
        //给 authorities 扩容（逻辑上的扩容。本质新建数组，再复制）
        authorities = Arrays.copyOf( authorities, authorities.length + roles.size() );
        for( Role role :roles ){
            //index 可以接着用，并且保存的值正好是当前数组空位上的索引
            authorities[index++] = role.getName();
        }


        //链式调用
        UserDetails userDetails =
        org.springframework.security.core.userdetails.User
                //此处是调用 builder()方法，和上面包名的点区分好
                .builder()
                //使用动态获取的结果,添加用户名
                .username(user.getUsername())
                //添加密码
                .password(user.getPassword())
                //用户权限和角色,可以给一个字符串数组
                .authorities( authorities )
                .build();
        return userDetails;
    }

    //1.首先验证班级的验证码
    //2.验证手机号是否重复
    @Override
    public void registerStudent(RegisterVo registerVo) throws ServiceException{
        //验证班级验证码

        //Mybatis-Plus 提供的条件构造器。
        QueryWrapper<ClassInfo> qw = new QueryWrapper<>();
        //where invitecode = #{invitecode}
        qw.eq("invite_code",registerVo.getInviteCode());
        //select * from class_info where invite_code = #{inviteCode}
        ClassInfo classInfo = classInfoMapper.selectOne( qw );

        //如果查询出的班级是 null, 说明验证码是错误的
        //不能让程序再继续执行下去。
        //通过异常机制，达到程序跳转的效果。
        if( classInfo == null ){
            throw ServiceException.unprocesableEntity("邀请码错误");
        }

        //2.验证手机号是否重复
        //where username = #{phone}
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username",registerVo.getPhone());
        //select * from user where username = #{username}
        User user1 = userMapper.selectOne( userQueryWrapper );
        // 如果能查到用户，说明手机号已经注册过了。不能再注册了
        if( user1 != null ){
            throw ServiceException.unprocesableEntity("手机号已被注册过");
        }

        //取出数据，给到 UserMapper, 写到数据库中。
        User user = new User();
        user.setNickName( registerVo.getNickname() );
        user.setUsername( registerVo.getPhone() );
        user.setPhone( registerVo.getPhone() );

        //创建加密工具实例
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        //保存密文
        String pwd = encoder.encode( registerVo.getPassword());
        //添加加密算法ID
        user.setPassword("{bcrypt}" + pwd );
        user.setClassId( classInfo.getId() ); //设置班级的 ID
        user.setIsEnabled(1); //当前用户允许使用
        user.setIsLocked(0);  //当前用户未锁定（权限可用）
        user.setGmtCreate( LocalDateTime.now() );  //设置账户的创建时间
        int r = userMapper.insert( user );
        //返回给 Controller 告知是否成功
        if( r != 1 ){
            throw ServiceException.busy();
        }

        //给当前注册学生添加身份
        UserRole ur = new UserRole();
        ur.setUserId( user.getId() );
        ur.setRoleId( 2 );
        int num = userRoleMapper.insert( ur );
        if( num != 1 ){
            throw ServiceException.busy();
        }
    }


    //现在有两份数据，一份在数据库表中保存，一份在缓存中保存，但是数据库表中数据发生变化时。
    //缓存数据并不会随之更新，会造成数据不一致，造成错误。
    //在这里，为了保证缓存与数据保持同步一致。
    //可以设置一个计时器，定期获取数据库信息，保持一定时间内数据同步。
    //Java提供一个 java.util.Timer 对象，计时器，可以在指定时间周期执行任务方法。
    //Timer 对象中有一个 schedule( 任务对象，延迟时间，周期时间 )；
    //1.构造块可以写多个。按照书写顺序执行，都早于构造方法
    private final Timer timer = new Timer();
    //构造块：好处不受重载构造器的影响，不管调用哪个构造器，一定会执行。
    {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
               teachers.clear();
               masterMap.clear();
            }
        }, 1000 * 60 * 10 , 1000 * 60 * 10 );
    }






    //参考列表的方案，提高访问速度，也制作一个缓存，保存老师信息。
    //约束对象
    //JUC（java.util.concurrent) 包下的线程安全的数据结构
    private final List<User> teachers = new CopyOnWriteArrayList<>();
    private final Map<String,User> masterMap = new ConcurrentHashMap<>();

    @Override
    public List<User> getMaster() {
        //如果没有，初始化teachers集合
        if( teachers.isEmpty()){
            synchronized (teachers){
                if(teachers.isEmpty()){
                    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("account_type",1);
                    List<User> list = userMapper.selectList(queryWrapper);
                    teachers.addAll( list );

                    for(User u:list){
                        masterMap.put( u.getNickName(), u);
                        //请求敏感信息，如密码，账户余额等（脱敏处理）
                        u.setPassword("");
                    }
                }
            }
        }
        //如果已经有值，直接返回。
        return teachers;
    }

    @Override
    public Map<String, User> getMasterMap() {
        if(masterMap.isEmpty()){
            getMaster();
        }
        return masterMap;
    }
}
